/* Turska Framework/UI library (Turs2)
 * Copyright 2003-2010 Jetro Lauha
 * All rights reserved.
 * Web: http://iki.fi/jetro/turska/
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of EITHER:
 *   (1) The GNU Lesser General Public License as published by the Free
 *       Software Foundation; either version 2.1 of the License, or (at
 *       your option) any later version. The text of the GNU Lesser
 *       General Public License is included with this library in the
 *       file LICENSE-LGPL.txt.
 *   (2) The BSD-style license that is included with this library in
 *       the file LICENSE-BSD.txt.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the files
 * LICENSE-LGPL.txt and LICENSE-BSD.txt for more details.
 *
 * $Id: T2IMGUI.cpp 28 2012-07-28 17:03:06Z trzntaat $
 * $Revision: 28 $
 */

#include <cstring>
#include "Turs2.h"

using namespace std;


namespace turska
{


IMGUI::IMGUI() :
    mAutoIdCounter(0),
    mMouseX(0), mMouseY(0),
    mMouseDown(false),
    mPreviousWidgetId(IMGUI_AWS_NONE),
    mHotMouseWidgetId(IMGUI_AWS_NONE), mOldHotMouseWidgetId(IMGUI_AWS_NONE),
    mActiveMouseWidgetId(IMGUI_AWS_NONE), mOldActiveMouseWidgetId(IMGUI_AWS_NONE),
    mActiveKeyWidgetId(IMGUI_AWS_NONE),
    mPreviousKeyWidgetId(IMGUI_AWS_NONE),
    mTime(0),
    mLastKeyEventTime(0)
{
    memset(&mKeyEvent, 0, sizeof(KeyEvent));
}


IMGUI::~IMGUI()
{
}


void IMGUI::begin(UI32 time, I16 groupId, bool renderingEnabled)
{
    mTime = time;
    mHotMouseWidgetId = IMGUI_AWS_NONE;
    mAutoIdCounter = (groupId & 0x7fff) << 16;
    mRenderingEnabled = renderingEnabled;
    mCurrentWidgetId = IMGUI_AWS_NONE;
}


void IMGUI::end()
{
    if (!mMouseDown)
        mActiveMouseWidgetId = IMGUI_AWS_NONE;
    else if (mActiveMouseWidgetId == IMGUI_AWS_NONE)
    {
        // without this some buttons can be triggered by dragging to in them from outside
        mActiveMouseWidgetId = IMGUI_AWS_UNAVAILABLE;
    }
    mCurrentWidgetId = IMGUI_AWS_NONE;
    if (mKeyEvent.action == KA_CHAR)
        mKeyEvent.action = KA_NONE; // nothing consumed the entered character
}


void IMGUI::resetState()
{
    mActiveKeyWidgetId = mPreviousKeyWidgetId = IMGUI_AWS_NONE;
    mActiveMouseWidgetId = mOldActiveMouseWidgetId = IMGUI_AWS_NONE;
    mHotMouseWidgetId = mOldHotMouseWidgetId = IMGUI_AWS_NONE;
    mMouseDown = false;
}


I32 IMGUI::defaultWidgetStateLogic(I32 id, bool hotNow, IMGUIWidgetStyle *style)
{
    mOldHotMouseWidgetId = mHotMouseWidgetId;
    mOldActiveMouseWidgetId = mActiveMouseWidgetId;

    if (hotNow)
    {
        mHotMouseWidgetId = id;
        if (mActiveMouseWidgetId == IMGUI_AWS_NONE && mMouseDown)
        {
            mActiveMouseWidgetId = id;
            if (mActiveKeyWidgetId != IMGUI_AWS_NONE)
                mActiveKeyWidgetId = id;
        }
    }

    I32 widgetState = IMGUI_WS_DEFAULT;
    if (mHotMouseWidgetId == id)
    {
        if (mActiveMouseWidgetId == id)
            widgetState = IMGUI_WS_HOT_ACTIVE;
        else
            widgetState = IMGUI_WS_HOT;
    }
    else if (mActiveMouseWidgetId == id)
        widgetState = IMGUI_WS_ACTIVE;

    if ((mActiveKeyWidgetId == id || mActiveKeyWidgetId == IMGUI_AWS_NONE) &&
        mKeyEvent.action == KA_PREVIOUS)
    {
        mActiveKeyWidgetId = mPreviousKeyWidgetId;
        mKeyEvent.action = KA_NONE;
        mLastKeyEventTime = mTime;
    }
    else if ((mPreviousWidgetId == mActiveKeyWidgetId || mActiveKeyWidgetId == IMGUI_AWS_NONE) &&
             mKeyEvent.action == KA_NEXT)
    {
        mActiveKeyWidgetId = id;
        if (!style || style->isKeyFocusAllowed())
            mKeyEvent.action = KA_NONE;
        mLastKeyEventTime = mTime;
    }

    if (mActiveKeyWidgetId == id)
    {
        widgetState = IMGUI_WS_ACTIVE;
        if (mActiveMouseWidgetId == id && mHotMouseWidgetId == id)
            widgetState = IMGUI_WS_ACTIVE | IMGUI_WS_HOT;
    }

    if (mActiveKeyWidgetId == id && style && !style->isKeyFocusAllowed())
    {
        // if this widget has key focus and it shouldn't, ask for next one...
        mKeyEvent.action = KA_NEXT;
        //mActiveKeyWidgetId = mPreviousKeyWidgetId; // or set it to previous?
        mLastKeyEventTime = mTime;
    }

    mPreviousWidgetId = id;
    if (style && style->isKeyFocusAllowed())
        mPreviousKeyWidgetId = id;

    return widgetState;
}


I32 IMGUI::buttonLogic(I32 id, IMGUIButtonStyle *buttonStyle,
                       TREAL x, TREAL y, TREAL width, TREAL height,
                       const C8 *text, Image *image)
{
    bool inside = false;
    Rectangle area(x, y, width, height);
    if (buttonStyle != 0)
        inside = buttonStyle->isInside(*this, (TREAL)mMouseX, (TREAL)mMouseY, area, text, image);
    else
        inside = area.isInside((TREAL)mMouseX, (TREAL)mMouseY);

    I32 widgetState = defaultWidgetStateLogic(id, inside, buttonStyle);

    return widgetState;
}


bool IMGUI::isButtonClicked(I32 id, IMGUIButtonStyle *style)
{
    bool clicked = false;

    if (!style || style->activatesFromUpEvent())
    {
        // action from button up-event
        if (!mMouseDown && mHotMouseWidgetId == id && mActiveMouseWidgetId == id)
            clicked = true;
    } else {
        // action from button down-event
        if (mMouseDown && mOldActiveMouseWidgetId == IMGUI_AWS_NONE && mActiveMouseWidgetId == id)
            clicked = true;
    }

    if (mActiveKeyWidgetId == id && mKeyEvent.action == KA_SELECT)
    {
        clicked = true;
        mKeyEvent.action = KA_NONE;
    }

    return clicked;
}


bool IMGUI::button(TREAL x, TREAL y, I32 id,
                   IMGUIButtonStyle *buttonStyle,
                   TREAL width, TREAL height,
                   const C8 *text, Image *image)
{
    if (id == IMGUI_AI_COUNTER)
        id = ++mAutoIdCounter;
    mCurrentWidgetId = id;
    
    I32 widgetState = buttonLogic(id, buttonStyle, x, y, width, height, text, image);
    
    if (buttonStyle)
        mLastRenderedRectangle = buttonStyle->render(*this, widgetState, x, y, width, height, text, image);

    return isButtonClicked(id, buttonStyle);
}


bool IMGUI::checkBox(TREAL x, TREAL y, bool &value, I32 id,
                     IMGUIButtonStyle *buttonStyle,
                     TREAL width, TREAL height,
                     const C8 *text, Image *image)
{
    // reset text & image when checkbox is disabled
    if (!value)
    {
        text = 0;
        image = 0;
    }

    bool buttonPressed = button(x, y, id, buttonStyle, width, height, text, image);
    if (buttonPressed)
        value = !value;

    return buttonPressed;
}


void IMGUI::slider(TREAL x, TREAL y, I32 &value, I32 id,
                   IMGUI_WIDGET_ORIENTATION axis,
                   IMGUISliderStyle *sliderStyle,
                   I32 minValue, I32 maxValue, I32 step,
                   TREAL width, TREAL height)
{
    if (id == IMGUI_AI_COUNTER)
        id = ++mAutoIdCounter;
    mCurrentWidgetId = id;

    bool inside = false;
    if (sliderStyle != 0)
    {
        Rectangle area(x, y, width, height);
        inside = sliderStyle->isInside(*this, (TREAL)mMouseX, (TREAL)mMouseY, area);
    }

    I32 widgetState = defaultWidgetStateLogic(id, inside, sliderStyle);

    if (sliderStyle != 0 &&
        (widgetState & IMGUI_WS_MASK_ACTIVE) != 0 &&
        mActiveMouseWidgetId == id)
    {
        Rectangle area(x, y, width, height);
        I32 newValue = sliderStyle->getIntValue(*this, axis, minValue, maxValue,
                                                (TREAL)mMouseX, (TREAL)mMouseY, area);
        value = newValue;
    }

    if ((widgetState & IMGUI_WS_MASK_ACTIVE) != 0 &&
        mActiveKeyWidgetId == id &&
        mKeyEvent.action != KA_NONE)
    {
        I32 shiftStepModifier, controlStepModifier;
        sliderStyle->getIntModifierMultipliers(shiftStepModifier, controlStepModifier);
        I32 actualStep = step;
        if (mKeyEvent.shift)
            actualStep *= shiftStepModifier;
        if (mKeyEvent.control)
            actualStep *= controlStepModifier;
        if (axis == IMGUI_WO_HORIZONTAL)
        {
            if (mKeyEvent.action == KA_LEFT && value > minValue)
            {
                value -= actualStep;
                mKeyEvent.action = KA_NONE;
            }
            if (mKeyEvent.action == KA_RIGHT && value < maxValue)
            {
                value += actualStep;
                mKeyEvent.action = KA_NONE;
            }
        }
        else if (axis == IMGUI_WO_VERTICAL)
        {
            if (mKeyEvent.action == KA_UP && value > minValue)
            {
                value -= actualStep;
                mKeyEvent.action = KA_NONE;
            }
            if (mKeyEvent.action == KA_DOWN && value < maxValue)
            {
                value += actualStep;
                mKeyEvent.action = KA_NONE;
            }
        }
        if (value < minValue)
            value = minValue;
        if (value > maxValue)
            value = maxValue;
    }

    if (sliderStyle)
        mLastRenderedRectangle = sliderStyle->render(*this, widgetState, axis, value, minValue, maxValue, x, y, width, height);
}


void IMGUI::slider(TREAL x, TREAL y, TREAL &value, I32 id,
                   IMGUI_WIDGET_ORIENTATION axis,
                   IMGUISliderStyle *sliderStyle,
                   TREAL minValue, TREAL maxValue, TREAL step,
                   TREAL width, TREAL height)
{
    if (id == IMGUI_AI_COUNTER)
        id = ++mAutoIdCounter;
    mCurrentWidgetId = id;

    bool inside = false;
    if (sliderStyle != 0)
    {
        Rectangle area(x, y, width, height);
        inside = sliderStyle->isInside(*this, (TREAL)mMouseX, (TREAL)mMouseY, area);
    }

    I32 widgetState = defaultWidgetStateLogic(id, inside, sliderStyle);

    if (sliderStyle != 0 && 
        (widgetState & IMGUI_WS_MASK_ACTIVE) != 0 &&
        mActiveMouseWidgetId == id)
    {
        Rectangle area(x, y, width, height);
        TREAL newValue = sliderStyle->getRealValue(*this, axis, minValue, maxValue,
                                                  (TREAL)mMouseX, (TREAL)mMouseY, area);
        value = newValue;
    }

    if ((widgetState & IMGUI_WS_MASK_ACTIVE) != 0 &&
        mActiveKeyWidgetId == id &&
        mKeyEvent.action != KA_NONE)
    {
        TREAL shiftStepModifier, controlStepModifier;
        sliderStyle->getRealModifierMultipliers(shiftStepModifier, controlStepModifier);
        TREAL actualStep = step;
        if (mKeyEvent.shift)
            actualStep *= shiftStepModifier;
        if (mKeyEvent.control)
            actualStep *= controlStepModifier;
        if (axis == IMGUI_WO_HORIZONTAL)
        {
            if (mKeyEvent.action == KA_LEFT && value > minValue)
            {
                value -= actualStep;
                mKeyEvent.action = KA_NONE;
            }
            if (mKeyEvent.action == KA_RIGHT && value < maxValue)
            {
                value += actualStep;
                mKeyEvent.action = KA_NONE;
            }
        }
        else if (axis == IMGUI_WO_VERTICAL)
        {
            if (mKeyEvent.action == KA_UP && value > minValue)
            {
                value -= actualStep;
                mKeyEvent.action = KA_NONE;
            }
            if (mKeyEvent.action == KA_DOWN && value < maxValue)
            {
                value += actualStep;
                mKeyEvent.action = KA_NONE;
            }
        }
        if (value < minValue)
            value = minValue;
        if (value > maxValue)
            value = maxValue;
    }

    if (sliderStyle)
        mLastRenderedRectangle = sliderStyle->render(*this, widgetState, axis, value, minValue, maxValue, x, y, width, height);
}


bool IMGUI::textField(TREAL x, TREAL y, C8 *string, I32 maxStringLength, I32 id,
                      IMGUITextFieldStyle *textFieldStyle, TREAL width, TREAL height)
{
    bool modified = false;

    /* Later TODO:
     * - caret position inside string
     * - support for selecting part of string
     * - copy/paste (probably only to example framework side)
     */
    if (id == IMGUI_AI_COUNTER)
        id = ++mAutoIdCounter;
    mCurrentWidgetId = id;
    
    bool inside = false;
    Rectangle area(x, y, width, height);
    if (textFieldStyle != 0)
        inside = textFieldStyle->isInside(*this, (TREAL)mMouseX, (TREAL)mMouseY, area);
    else
        inside = area.isInside((TREAL)mMouseX, (TREAL)mMouseY);

    I32 widgetState = defaultWidgetStateLogic(id, inside, textFieldStyle);

    if (mMouseDown && mOldActiveMouseWidgetId != mActiveMouseWidgetId && mActiveMouseWidgetId == id)
        mActiveKeyWidgetId = id;

    if (mKeyEvent.action == KA_CHAR &&
        (((widgetState & IMGUI_WS_MASK_ACTIVE) != 0 && mActiveKeyWidgetId == id)))
    {
        const I32 stringLength = (I32)strlen(string);
        if (stringLength > 0 && mKeyEvent.character == 8)
        {
            // backspace
            string[stringLength - 1] = 0;
            modified = true;
        }
        else if (stringLength < maxStringLength &&
                 textFieldStyle->isCharAllowed((UI8)mKeyEvent.character))
        {
            string[stringLength] = (UI8)mKeyEvent.character;
            string[stringLength + 1] = 0;
            modified = true;
        }
        mLastKeyEventTime = mTime;
        mKeyEvent.action = KA_NONE;
    }

    if (mKeyEvent.action == KA_SELECT &&
        (((widgetState & IMGUI_WS_MASK_ACTIVE) != 0 && mActiveKeyWidgetId == id)))
    {
        mActiveKeyWidgetId = IMGUI_AWS_NONE;
    }

    if (textFieldStyle)
        mLastRenderedRectangle = textFieldStyle->render(*this, widgetState, x, y, width, height, string);

    return modified;
}


bool IMGUI::drop(TREAL x, TREAL y, I32 &dragId, IMGUIButtonStyle *buttonStyle,
                 TREAL width, TREAL height, const C8 *text, Image *image)
{
    if (mActiveMouseWidgetId == IMGUI_AWS_NONE || mActiveMouseWidgetId == IMGUI_AWS_UNAVAILABLE ||  // must have active mouse widget id
        mActiveMouseWidgetId == mHotMouseWidgetId ||    // can't drop on itself
        mMouseDown)     // if mouse is down, nothing is dropped yet
        return false;

    bool inside = false;
    Rectangle area(x, y, width, height);
    if (buttonStyle != 0)
        inside = buttonStyle->isInside(*this, (TREAL)mMouseX, (TREAL)mMouseY, area, text, image);
    else
        inside = area.isInside((TREAL)mMouseX, (TREAL)mMouseY);
    dragId = mActiveMouseWidgetId;

    return inside;
}


void IMGUI::onKeyDown(const KeyEvent &key)
{
    mKeyEvent = key;
    mKeyEvent.platformEvent = 0;
}


void IMGUI::onMouseDown()
{
    mMouseDown = true;
}


void IMGUI::onMouseUp()
{
    mMouseDown = false;
}


void IMGUI::onMouseMotion(I32 x, I32 y)
{
    mMouseX = x;
    mMouseY = y;
}


} // namespace turska
